#include "lini.h"
#include "lini_builder.h"
#include "lini_parse.h"
#include "lini_typedef.h"
#include <pthread.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdbool.h>
#include <unistd.h>



struct _GroupNode;

#define MAX_COMMENT_BUF_SIZE	128

struct _Lini
{
	struct _GroupNode *group_node;
	struct _GroupNode *current_group;
    pthread_mutex_t lock;

	char *settings_file_name;
	unsigned char have_comment_flag;
	char comment_buffer[MAX_COMMENT_BUF_SIZE];
};

typedef struct _PrivInfo
{
	Lini *settings;
	
}PrivInfo;

#define COMMENT_CHAR "#" 		/*ini file with this char show that this line is the comment line*/

struct _KeyNode
{
	char *key_string;
	char *value_string;
	char *comment;
	struct _KeyNode *next_key_node;
};

struct _GroupNode
{
	char *group_string;
	char *comment;
	struct _GroupNode *next_group;
	struct _KeyNode *key_node;
	struct _KeyNode *cur_node;
};

static struct _KeyNode *key_node_create(Lini *settings, char *key, char *value)
{
	return_val_if_fail(settings && key , NULL);
	
	struct _KeyNode *key_node = (struct _KeyNode *)malloc(sizeof(*key_node));

	key_node->key_string = strdup(key);
	key_node->value_string = strdup(value);
	key_node->next_key_node = NULL;
	
	if(settings->have_comment_flag){	
		key_node->comment = strdup(settings->comment_buffer);
		settings->have_comment_flag = 0;
	}

	return key_node;
}

static int _groupNode_add_keyNode(struct _GroupNode *group_node, struct _KeyNode *key_node)
{
	return_val_if_fail(group_node, -1);
	return_val_if_fail(key_node, -1);
	if(group_node && key_node){	
		if(group_node->key_node == NULL){	
			group_node->key_node = key_node;
			group_node->cur_node = key_node;
		}
		else{
			group_node->cur_node->next_key_node = key_node;
			group_node->cur_node = key_node;
			group_node->cur_node->next_key_node = NULL;
		}
		
		return 0;
	}
	
	return -1;
}

static int _settings_add_key_value(Lini *settings, char *key, char *value)
{
	return_val_if_fail(settings != NULL && key != NULL && value != NULL, -1);

	if(settings->group_node != NULL){	
		struct _KeyNode *key_node = key_node_create(settings, key, value);	
		return_val_if_fail(key_node != NULL, -1);
		
		struct _GroupNode *current_group = settings->current_group;
		
		if(current_group){
			return _groupNode_add_keyNode(current_group, key_node);
		}
	}
	
	return -1;
}

static int _settings_add_group(Lini* settings, char* group)
{
	return_val_if_fail(settings != NULL && group != NULL, -1);

	/*create a group node*/
	struct _GroupNode *temp_group_node  = (struct _GroupNode*)malloc(sizeof(struct _GroupNode));
	return_val_if_fail(temp_group_node != NULL , -1);
	
	temp_group_node->group_string = strdup(group);
	return_val_if_fail(temp_group_node->group_string != NULL , -1);
	
	temp_group_node->key_node = NULL;
	temp_group_node->next_group = NULL;
	if(settings->have_comment_flag == 1 &&  settings->comment_buffer != NULL){	
		temp_group_node->comment = strdup(settings->comment_buffer);
		settings->have_comment_flag = 0;
	}
	/*add the group to group list*/
	if(settings->group_node == NULL){
		settings->group_node = temp_group_node;
		settings->current_group = temp_group_node;
	}
	else{
		settings->current_group->next_group = temp_group_node;
		settings->current_group = temp_group_node;
	}
	
	return 0;
}

static int _settings_store_comment(Lini *settings, char *comment)
{
	return_val_if_fail(settings && comment, -1);
	
	settings->have_comment_flag = 1;

	strncpy(settings->comment_buffer, comment, MAX_COMMENT_BUF_SIZE);
	
	return 0;
}

static void  _settings_builder_on_group(LiniBuilder *thiz, char *group)
{
	return_if_fail(thiz != NULL && group != NULL);
	
	DECL_PRIV(thiz, priv);
	
	_settings_add_group(priv->settings, group);

	return;
}

static void _settings_builder_on_key_value(LiniBuilder *thiz, char *key, char *value)
{
	return_if_fail(thiz != NULL && key != NULL);
	
	DECL_PRIV(thiz, priv);
	
	_settings_add_key_value(priv->settings, key, value);
	
	return;
}

static void _settings_builder_on_comment(LiniBuilder *thiz, char *comment)
{
	return_if_fail(thiz != NULL && comment != NULL);

	DECL_PRIV(thiz, priv);
	
	_settings_store_comment(priv->settings, comment);
	
	return;
}

static void _settings_builder_destroy(LiniBuilder *thiz)
{
	if(thiz){	
		thiz->ref_count--;
		if(thiz->ref_count <= 0){
			free(thiz->priv);
			free(thiz);
		}
	}
}

static LiniBuilder *lini_builder_create(void)
{
	LiniBuilder *thiz = (LiniBuilder *)malloc(sizeof(LiniBuilder));
	return_val_if_fail(thiz != NULL, NULL);
	thiz->priv = (char*)malloc(sizeof(PrivInfo));

	if(thiz != NULL ){
		thiz->on_group = _settings_builder_on_group;
		thiz->on_key_value = _settings_builder_on_key_value;
		thiz->on_comment = _settings_builder_on_comment;
		thiz->destroy = _settings_builder_destroy;	
		thiz->ref_count = 0;
	}

	return thiz;
}

static  int lini_parser_file(Lini *thiz)
{
	return_val_if_fail(thiz != NULL && thiz->settings_file_name != NULL, -1);
	
	LiniParser *parser = NULL;
	LiniBuilder *builder = NULL;
	FILE* fp;
	struct stat st = {0};
	unsigned char* data = NULL;
	
	/*create the ini parser*/
	parser = lini_parser_create();

	if(stat(thiz->settings_file_name, &st) != 0){
		return -1;
	}

	fp = fopen(thiz->settings_file_name, "rb");
	if(fp != NULL){
		data = malloc(st.st_size + 1);
		fread(data, st.st_size, 1, fp);
		fclose(fp);
	}
	else{
        perror("open settings file failed!\n");
        return -1;
	}

	/*create the builder*/
	builder = lini_builder_create();
	if(builder == NULL){		
		free(data);
		lini_parser_destory(parser);
		return -1;
	}	

	DECL_PRIV(builder, priv);
	priv->settings = thiz;
	
	lini_parser_set_builder(parser, builder);

	lini_parser_parse(parser, (char*)data, COMMENT_CHAR);

	free(data);
	lini_parser_destory(parser);
	lini_builder_destroy(builder);

	return 0;
}		


Lini* lini_create(char *settings_file)
{	
	return_val_if_fail(settings_file != NULL, NULL);
	
	Lini *thiz = NULL;

	thiz = (Lini *)malloc(sizeof(Lini));
	return_val_if_fail(thiz != NULL, NULL);
	
    pthread_mutex_init(&thiz->lock, NULL);   

	thiz->settings_file_name = strdup(settings_file);
	
	thiz->current_group = NULL;
	thiz->group_node = NULL;
	
	lini_parser_file(thiz);

	return  thiz;
}

int lini_get_value(Lini *thiz, char *group, char* key, char *default_value, char** value)
{
	return_val_if_fail(thiz && group && key, RET_INI_ERROR);


	pthread_mutex_lock(&thiz->lock);
	struct _GroupNode *group_node = thiz->group_node;
	struct _KeyNode *key_node = NULL;
	struct _KeyNode* new_key_node = NULL;
	struct _GroupNode* prev_group = group_node;

	return_val_if_fail(group_node != NULL, RET_INI_ERROR);
	
	for(;group_node != NULL; group_node = group_node->next_group){	
	    prev_group = group_node;
		if(group_node->group_string != NULL && strcmp(group_node->group_string, group) == 0){	
			key_node = group_node->key_node;
			for(;key_node ;key_node = key_node->next_key_node){
				if(key_node->key_string != NULL && strcmp(key_node->key_string, key) == 0){
					pthread_mutex_unlock(&thiz->lock);
					*value = key_node->value_string;
					return RET_INI_OK;    
				}
			}
			            
		new_key_node = key_node_create(thiz, key, default_value);	
			
		_groupNode_add_keyNode(group_node, new_key_node);

		*value = new_key_node->value_string;
		pthread_mutex_unlock(&thiz->lock);
		return  RET_INI_MODIFY;		
		}
	}

	struct _GroupNode *temp_group_node  = (struct _GroupNode*)malloc(sizeof(struct _GroupNode));
	if(temp_group_node == NULL){
		return RET_INI_ERROR;
	}
	temp_group_node->group_string = strdup(group);
	temp_group_node->key_node = NULL;
	temp_group_node->next_group = NULL;

    new_key_node = key_node_create(thiz, key, default_value);	
				
    _groupNode_add_keyNode(temp_group_node, new_key_node);
	
    prev_group->next_group = temp_group_node;
    
    *value = new_key_node->value_string;
    pthread_mutex_unlock(&thiz->lock);

	return RET_INI_MODIFY;    
}


int lini_set_value(Lini *thiz, char *group, char* key, char* value)
{
	return_val_if_fail(thiz && group && key, -1);

		
	pthread_mutex_lock(&thiz->lock);
	struct _GroupNode *group_node = thiz->group_node;
	struct _KeyNode *key_node = NULL;
	for(;group_node != NULL; group_node = group_node->next_group){	
		if(group_node->group_string != NULL && strcmp(group_node->group_string, group) == 0)
		{	
			key_node = group_node->key_node;
			for(;key_node ;key_node = key_node->next_key_node)
			{
				if(key_node->key_string != NULL && (strcmp(key_node->key_string, key) == 0))
				{
					free(key_node->value_string);
					key_node->value_string = (char*) strdup(value);
					pthread_mutex_unlock(&thiz->lock);

					return 0;
				}
			}
		}
	}	

	pthread_mutex_unlock(&thiz->lock);
	return -1;
}


static int write_group_to_file(struct _GroupNode *group_node,  int fd)
{
	return_val_if_fail(group_node && fd >= 0,  -1);

	char  buf[512] = {0};
	
	if(group_node->comment){
		sprintf(buf, "%s%s\n", COMMENT_CHAR, group_node->comment);
		write(fd, buf, strlen(buf));
	}

	if(group_node->group_string){
		sprintf(buf,  "[%s]\n", group_node->group_string);
		write(fd, buf, strlen(buf));
	}

	return 0;
}


static int write_key_node_to_file(struct _KeyNode *key_node, int fd)
{
	return_val_if_fail(key_node != NULL && fd >= 0, -1);
	
	char  buf[512] = {0};
	if(key_node->comment){
		sprintf(buf, "%s%s\n", COMMENT_CHAR, key_node->comment);
		write(fd, buf, strlen(buf));
	}
	
	if(key_node->key_string && key_node->value_string){
		sprintf(buf,  "%s=%s\n", key_node->key_string, key_node->value_string);
		write(fd, buf, strlen(buf));
	}	
	
	return 0;
}

int lini_sync(Lini *thiz)
{
	return_val_if_fail(thiz != NULL, -1);
    pthread_mutex_lock(&thiz->lock);
	
	int fd = -1;
	char* temp_file = "temp";
	char buffer[64] = {0};
	char utf8_bytes[4] = {0};
	int ret = -1;
	
	fd = open(temp_file, O_CREAT| O_WRONLY);
	if(fd < 0){
        perror("Error:hdy_ini_sync Open file failed!\n");
        ret = -1;
        goto OUT;
	}
	
	utf8_bytes[0] = 0xEF;
	utf8_bytes[1] = 0xBB;
	utf8_bytes[2] = 0xBF;
	ret = write(fd, utf8_bytes, 3);
	if(ret < 0){
	    perror("write failed!\n");
		close(fd);
		return -1;
	}
	
	struct _GroupNode *group_node = thiz->group_node;
	struct _KeyNode *key_node = NULL;
	for(;group_node != NULL; group_node = group_node->next_group){
		write_group_to_file(group_node, fd);
		key_node = group_node->key_node;
		for(;key_node != NULL;key_node = key_node->next_key_node){
			write_key_node_to_file(key_node, fd);
		}
	}

	write(fd, "\n", 1);
	
	close(fd);
	fd = -1;

	sprintf(buffer, "chmod 666 %s", temp_file);
	system(buffer);
	
	unlink(thiz->settings_file_name);

	sprintf(buffer, "cp -f %s %s", temp_file, thiz->settings_file_name);
	system(buffer);
		
	sprintf(buffer, "chmod 666 %s", thiz->settings_file_name);

	system(buffer);


OUT:
    pthread_mutex_unlock(&thiz->lock);
	return ret > 0 ?0:-1;
}


void lini_destroy(Lini *thiz)
{
	if(thiz){
		free(thiz->settings_file_name);

		struct _GroupNode *group_node = thiz->group_node;
		struct _GroupNode *temp_group_node;
		
		for( ;group_node != NULL; ){
			struct _KeyNode *key_node = group_node->key_node;
			struct _KeyNode *temp_key_node = NULL;

			for( ;key_node != NULL; ){
				temp_key_node = key_node;
				key_node = key_node->next_key_node;
				
				free(temp_key_node->key_string);
				free(temp_key_node->value_string);
				free(temp_key_node->comment);
				free(temp_key_node);
			}
			
			temp_group_node = group_node;
			group_node = group_node->next_group;

			free(temp_group_node->group_string);
			free(temp_group_node->comment);
			free(temp_group_node);
		}		
		
		pthread_mutex_destroy( &thiz->lock );
		
		free(thiz);
	}	
	
	return;
}

